#include <ctype.h>
#include <errno.h>
#include <linux/types.h>
#include <linux/virtio_ring.h>
#include <linux/virtio_config.h>
#include <linux/virtio_net.h>
#include <linux/virtio_blk.h>
#include <linux/virtio_console.h>
#include <linux/virtio_rng.h>

#include <signal.h>
#include <sched.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/eventfd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>

#include "Devices.h"
#include "DeviceHandler.h"
#include "MemoryManagement.h"

//Macro para el manejo de las funciones de chequeo de punteros
//Evita que los punteros sobrepasen el límite actual de la memoria establecido
void *_check_pointer(unsigned long addr, unsigned int size,
			    unsigned int line)
{
	//Evitamos wrap around
	if (addr >= guest_current_limit || addr + size >= guest_current_limit)
		errx(1, "%s:%i: Dirección Inválida %#lx", __FILE__, line, addr);
	return to_guest_logical_adress(addr);
}

#define check_pointer(addr,size) _check_pointer(addr, size, __LINE__)

//Obtiene el siguiente descriptor ya que en realidad en el anillo lo que existen son descriptores
unsigned next_desc(struct vring_desc *desc, unsigned int i, unsigned int max)
{
	unsigned int next;

	//Si este descriptor no esta en la cola
	if (!(desc[i].flags & VRING_DESC_F_NEXT))
		return max;

	//Cheque a ver si el siguiente desctiprot sobrepasa o no al siguiente
	next = desc[i].next;
	wmb();

	if (next >= max)
		errx(1, "El siguiente descriptor es %u", next);
	return next;
}

//Utiliza el buffer y envia la interrupción
void trigger_irq(struct virtqueue *vq)
{
	unsigned long buf[] = { LHREQ_IRQ, vq->config.irq };

	// Si no sucedio nada, entonces no se informa nada
	if (!vq->pending_used)
		return;
	vq->pending_used = 0;

	// Si se desae que se de la interrupción se envia si no se retorna
	if (vq->vring.avail->flags & VRING_AVAIL_F_NO_INTERRUPT) {
		// si se fuerza una interrupción
		if (!vq->dev->irq_on_empty || last_avail(vq) != vq->vring.avail->idx)
			return;
	}

	// Se envia al guest una interrupción diciendo que se envio algo
	if (write(guest_fd, buf, sizeof(buf)) != 0)
		err(1, "Activando el irq %i", vq->config.irq);
}

/*
 * This looks in the virtqueue for the first available buffer, and converts
 * it to an iovec for convenient access.  Since descriptors consist of some
 * number of output then some number of input descriptors, it's actually two
 * iovecs, but we pack them into one and note how many of each there were.
 *
 * This function waits if necessary, and returns the descriptor number found.
 */
unsigned wait_for_vq_desc(struct virtqueue *vq,
				 struct iovec iov[],
				 unsigned int *out_num, unsigned int *in_num)
{
	unsigned int i, head, max;
	struct vring_desc *desc;
	u16 last_avail = last_avail(vq);

	//Espera a que haya algo disponible
	while (last_avail == vq->vring.avail->idx) {
		u64 event;

		//Se informa al guest los recursos que se han usado
		trigger_irq(vq);

		//Buscamos descriptores agregados nuevos
		vq->vring.used->flags &= ~VRING_USED_F_NO_NOTIFY;

		//Se espera a que se escriban todos por prevención, se verififcan de nuevo
		mb();
		if (last_avail != vq->vring.avail->idx) {
			vq->vring.used->flags |= VRING_USED_F_NO_NOTIFY;
			break;
		}

		// Se espera a que se rrellenen los buffers
		if (read(vq->eventfd, &event, sizeof(event)) != sizeof(event))
			errx(1, "Fallo el evento de lectura");

		/* We don't need to be notified again. */
		vq->vring.used->flags |= VRING_USED_F_NO_NOTIFY;
	}

	// Se chequean los descriptores para que este sanitizados
	if ((u16)(vq->vring.avail->idx - last_avail) > vq->vring.num)
		errx(1, "Guest moved used index from %u to %u",
		     last_avail, vq->vring.avail->idx);

	// Se escribe el mas reciente
	head = vq->vring.avail->ring[last_avail % vq->vring.num];
	last_avail(vq)++;

	// Se verifica que este en rango
	if (head >= vq->vring.num)
		errx(1, "Error fatal %u se encuentra ocupado y se muestra como disponible", head);

	// Cuando se incia el input y el output
	*out_num = *in_num = 0;

	max = vq->vring.num;
	desc = vq->vring.desc;
	i = head;

	//Se verifica la validez de la entrada indirecta
	if (desc[i].flags & VRING_DESC_F_INDIRECT) {
		if (desc[i].len % sizeof(struct vring_desc))
			errx(1, "Estructura de indexación de buffer esta corrrupta");

		max = desc[i].len / sizeof(struct vring_desc);
		desc = check_pointer(desc[i].addr, desc[i].len);
		i = 0;
	}

	do {
        //Se verifica la validez del primer descriptor
		iov[*out_num + *in_num].iov_len = desc[i].len;
		iov[*out_num + *in_num].iov_base
			= check_pointer(desc[i].addr, desc[i].len);
		//Si es input se incrementa el contador
		if (desc[i].flags & VRING_DESC_F_WRITE)
			(*in_num)++;
		else {
			if (*in_num)
				errx(1, "Se esperan que los outputs vengan primero que los inputs");
			(*out_num)++;
		}

		// Si hay demasiados descriptores entonces existe un error
		if (*out_num + *in_num > max)
			errx(1, "Descriptor recursivo");
	} while ((i = next_desc(desc, i, max)) != max);

	return head;
}

//Se le indica al guest que  un buffer fue utilizado
void add_used(struct virtqueue *vq, unsigned int head, int len)
{
	struct vring_used_elem *used;

	// El virtqueue tiene un puntero alos buffers usasos, se envia la siguiente entrada en ese anillo
	used = &vq->vring.used->ring[vq->vring.used->idx % vq->vring.num];
	used->id = head;
	used->len = len;
	/* Se asegura que se escirba el buffer antes de enviarlo. */
	wmb();
	vq->vring.used->idx++;
	vq->pending_used++;
}

/* Añade un buffer y genera una interrupción */
void add_used_and_trigger(struct virtqueue *vq, unsigned head, int len)
{
	add_used(vq, head, len);
	trigger_irq(vq);
}



//Maeneja el consumo de las entradas de una consola
void console_input(struct virtqueue *vq)
{
	int len;
	unsigned int head, in_num, out_num;
	struct console_abort *abort = vq->dev->priv;
	struct iovec iov[vq->vring.num];

	//Busca un descriptor
	head = wait_for_vq_desc(vq, iov, &out_num, &in_num);
	if (out_num)
		errx(1, "No hay descriptores disponibles en el anillo");

	//Se espera al que le lea
	len = readv(STDIN_FILENO, iov, in_num);
	if (len <= 0) {
		/* No hay input */
		warnx("Ha habido problemas con la estructura");
		for (;;)
			pause();
	}

	//Se indica que se uso un buffer
	add_used_and_trigger(vq, head, len);

	//Se mide la cantidad de solicitudes para salir del sistema
	if (len != 1 || ((char *)iov[0].iov_base)[0] != 3) {
		abort->count = 0;
		return;
	}

	abort->count++;
	if (abort->count == 1)
		gettimeofday(&abort->start, NULL);
	else if (abort->count == 3) {
		struct timeval now;
		gettimeofday(&now, NULL);
		//Mata los procesos
		if (now.tv_sec <= abort->start.tv_sec+1)
			kill(0, SIGINT);
		abort->count = 0;
	}
}

//Maneja el output a la consola
void console_output(struct virtqueue *vq)
{
	unsigned int head, out, in;
	struct iovec iov[vq->vring.num];

	//Se espera que el guest
	head = wait_for_vq_desc(vq, iov, &out, &in);
	if (in)
		errx(1, "Error con el output del sistema");

	/* Se espera que el wite complete. */
	while (!iov_empty(iov, out)) {
		int len = writev(STDOUT_FILENO, iov, out);
		if (len <= 0)
			err(1, "La escritura al stdout dio como resultado %i", len);
		iov_consume(iov, out, len);
	}
	add_used(vq, head, 0);
}

/* Crea un thread y ejecuta en forma ciclica una funcipón de atención
 * a una cola virtual
 */
int do_thread(void *_vq)
{
	struct virtqueue *vq = _vq;

	for (;;)
		vq->service(vq);
	return 0;
}

// Mata el proceso
void kill_launcher(int signal)
{
	kill(0, SIGTERM);
}

//hACE UN RESET AL DISPOSITIVO
void reset_device(struct device *dev)
{
	struct virtqueue *vq;

	//Borra las capacidades reconocidad
	memset(get_feature_bits(dev) + dev->feature_len, 0, dev->feature_len);

	//Elimina los threads hijos
	signal(SIGCHLD, SIG_IGN);

	// Borra el contenido de las colas y los threads
	for (vq = dev->vq; vq; vq = vq->next) {
		if (vq->thread != (pid_t)-1) {
			kill(vq->thread, SIGTERM);
			waitpid(vq->thread, NULL, 0);
			vq->thread = (pid_t)-1;
		}
		memset(vq->vring.desc, 0,
		       vring_size(vq->config.num, VRING_ALIGN));
		last_avail(vq) = 0;
	}
	dev->running = false;

	//Mata todos los threads que aun se encuentren en ejecución
	signal(SIGCHLD, (void *)kill_launcher);
}

// Crea un thread que atiende a la cola de dispositivos
void create_thread(struct virtqueue *vq)
{
	// Crea una pila para el thread y dado que crece de manera ascendente se asigna al final de la región
	char *stack = malloc(32768);
	unsigned long args[] = { LHREQ_EVENTFD,
				 vq->config.pfn*getpagesize(), 0 };

	vq->eventfd = eventfd(0, 0);
	if (vq->eventfd < 0)
		err(1, "Error al crear el eventfd");
	args[2] = vq->eventfd;

	/*Evento que se genera al ser señalado por el guest
	 */
	if (write(guest_fd, &args, sizeof(args)) != 0)
		err(1, "Adjuntando evento eventfd");

	vq->thread = clone(do_thread, stack + 32768, CLONE_VM | SIGCHLD, vq);
	if (vq->thread == (pid_t)-1)
		err(1, "Clonando memoria de un thread hijo");

	close(vq->eventfd);
}

//Obtenemos si una caracteristica esta o no habilitada
bool accepted_feature(struct device *dev, unsigned int bit)
{
	const u8 *features = get_feature_bits(dev) + dev->feature_len;

	if (dev->feature_len < bit / CHAR_BIT)
		return false;
	return features[bit / CHAR_BIT] & (1 << (bit % CHAR_BIT));
}

//Inicializa los datos de un dispositivo
void start_device(struct device *dev)
{
	struct virtqueue *vq;
	dev->irq_on_empty = accepted_feature(dev, VIRTIO_F_NOTIFY_ON_EMPTY);

	for (vq = dev->vq; vq; vq = vq->next) {
		if (vq->service)
			create_thread(vq);
	}
	dev->running = true;
}

//Borra los datos del dispositivo
void cleanup_devices(void)
{
	struct device *dev;

	for (dev = devices.dev; dev; dev = dev->next)
		reset_device(dev);

	if (orig_term.c_lflag & (ISIG|ICANON|ECHO))
		tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
}

/* Manejo de la actualización del estado de los dispositivos*/
void update_device_status(struct device *dev)
{
	if (dev->desc->status == 0)
		reset_device(dev);
	else if (dev->desc->status & VIRTIO_CONFIG_S_FAILED) {
		warnx("Fallo la %s configuración", dev->name);
		if (dev->running)
			reset_device(dev);
	} else if (dev->desc->status & VIRTIO_CONFIG_S_DRIVER_OK) {
		if (!dev->running)
			start_device(dev);
	}
}

// Notifica los cambios del estado de los dispositivos durante el arranque del sistema
void handle_output(unsigned long addr)
{
	struct device *i;

	// Revisa los dispositivos
	for (i = devices.dev; i; i = i->next) {
		struct virtqueue *vq;

		// Si estan notificados significa que ya fueron actualizados
		if (to_guest_logical_adress(addr) == i->desc) {
			update_device_status(i);
			return;
		}

		//Se inicilizan los dispotivos y se les envian notificaciones , estos las atrapan cuando han cargado
		// y su estado es Ok
		for (vq = i->vq; vq; vq = vq->next) {
			if (addr != vq->config.pfn*getpagesize())
				continue;
			if (i->running)
				errx(1, "Se ha presentado una notificación cuando el dispositivo esta corriendo %s", i->name);
			start_device(i);
			return;
		}
	}

	//Se agrega terminales a los tring escritos en consola.

	if (addr >= guest_current_limit)
		errx(1, "Mala notificación %#lx", addr);

	write(STDOUT_FILENO, to_guest_logical_adress(addr),
	      strnlen(to_guest_logical_adress(addr), guest_current_limit - addr));
};
